package indi.zhifa.recipe.bailan.busy.aop;

import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.EntityPath;
import com.querydsl.core.types.Path;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.dsl.*;
import indi.zhifa.recipe.bailan.busy.entity.annotation.JpaQsdl;
import indi.zhifa.recipe.bailan.framework.common.entity.exception.ServiceException;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.core.annotation.Order;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.querydsl.EntityPathResolver;
import org.springframework.data.querydsl.SimpleEntityPathResolver;
import org.springframework.data.querydsl.binding.*;
import org.springframework.data.util.CastUtils;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Parameter;
import java.util.Optional;

@Slf4j
@Aspect
@Component
@Order(1)
public class LogicDelApo extends BaseAop {

    private final EntityPathResolver entityPathResolver;
    private final QuerydslBindingsFactory bindingsFactory;
    private final QuerydslPredicateBuilder predicateBuilder;

    public LogicDelApo(ObjectProvider<EntityPathResolver> resolver, Optional<ConversionService> conversionService){
        QuerydslBindingsFactory factory = new QuerydslBindingsFactory((EntityPathResolver)resolver.getIfUnique(() -> {
            return SimpleEntityPathResolver.INSTANCE;
        }));
        this.entityPathResolver = (EntityPathResolver)resolver.getIfUnique(() -> {
            return SimpleEntityPathResolver.INSTANCE;
        });
        this.bindingsFactory = factory;
        this.predicateBuilder = new QuerydslPredicateBuilder((ConversionService)conversionService.orElseGet(DefaultConversionService::new), factory.getEntityPathResolver());
    }


    @Around("execution(* indi.zhifa.recipe..*.*controller.api..*(..)) && @annotation(permission)")
    public Object around(ProceedingJoinPoint joinPoint, JpaQsdl permission) throws Throwable {
        Object[] args = joinPoint.getArgs();
        for(int i = 0; i < args.length; ++i) {
            if (args[i] instanceof Predicate) {
                Predicate query = (Predicate)args[i];
                MethodSignature signature = (MethodSignature)joinPoint.getSignature();
                Parameter[] methodParameters = signature.getMethod().getParameters();
                Parameter parameter = methodParameters[i];
                QuerydslPredicate predicate = (QuerydslPredicate)parameter.getDeclaredAnnotation(QuerydslPredicate.class);
                Assert.notNull(parameter, "Predicate参数的@QuerydslPredicate注解必须声明！");
                MultiValueMap<String, String> parameters = new LinkedMultiValueMap();
                parameters.add("del","0");

                args[i] = this.createPredicate(parameters, query, predicate);
                return joinPoint.proceed(args);
            }
        }
        return joinPoint.proceed();
    }

    private Predicate createPredicate(MultiValueMap<String, String> parameters, Predicate rightQuery, QuerydslPredicate predicate) {
        Optional<QuerydslPredicate> annotation = Optional.ofNullable(predicate);
        TypeInformation<?> domainType = (TypeInformation)annotation.filter((it) -> {
            return !Object.class.equals(it.root());
        }).map((it) -> {
            return ClassTypeInformation.from(it.root());
        }).orElse(null);
        Assert.notNull(domainType, "root不能为空");
        Optional<Class<? extends QuerydslBinderCustomizer<EntityPath<?>>>> bindingsAnnotation = annotation.map(QuerydslPredicate::bindings).map(CastUtils::cast);
        QuerydslBindings bindings = createBindings(bindingsAnnotation, domainType);
        Predicate permissionPredicate = this.predicateBuilder.getPredicate(domainType, parameters, bindings);
        BooleanBuilder builder = new BooleanBuilder(permissionPredicate);
        return builder.and(rightQuery);
    }

    private QuerydslBindings createBindings(Optional<Class<? extends QuerydslBinderCustomizer<EntityPath<?>>>> bindingsAnnotation, TypeInformation<?> domainType) {
        QuerydslBindings bindings = (QuerydslBindings)bindingsAnnotation.map((it) -> {
            return this.bindingsFactory.createBindingsFor(domainType, it);
        }).orElseGet(() -> {
            return this.bindingsFactory.createBindingsFor(domainType);
        });
        EntityPath path = this.entityPathResolver.createPath(domainType.getType());
        try {
            // 取得SimplePath的构造函数（源代码是protect的）
            Constructor<BooleanPath> appPathConstructor = BooleanPath.class.getDeclaredConstructor(Path.class, String.class);
            appPathConstructor.setAccessible(true);
            BooleanPath delPath = appPathConstructor.newInstance(path, "del");
            bindings.bind(delPath).first(SimpleExpression::eq);
        } catch (IllegalAccessException | InvocationTargetException | InstantiationException | NoSuchMethodException var12) {
            throw new ServiceException(var12.getMessage());
        }
        return bindings;
    }
}
